Skip to main content

winsafe\kernel\utilities/
path.rs

1//! File path utilities.
2//!
3//! Some of the functions are similar to [`std::path::Path`] ones, but here they
4//! work directly upon [`&str`](str) instead of [`&OsStr`](std::ffi::OsStr).
5
6use crate::co;
7use crate::decl::*;
8use crate::kernel::iterators::*;
9use crate::prelude::*;
10
11/// Returns an iterator over the files and folders within a directory. Does not
12/// search recursively.
13///
14/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
15/// iteration functions.
16///
17/// # Examples
18///
19/// ```no_run
20/// use winsafe::{self as w, prelude::*};
21///
22/// // Ordinary for loop
23/// for file_path in w::path::dir_list_flat("C:\\Temp") {
24///     let file_path = file_path?;
25///     println!("{}", file_path);
26/// }
27///
28/// // Collecting into a Vec
29/// let all = w::path::dir_list_flat("C:\\Temp")
30///     .collect::<w::SysResult<Vec<_>>>()?;
31///
32/// // Transforming and collecting into a Vec
33/// let all = w::path::dir_list_flat("C:\\Temp")
34///     .map(|file_path| {
35///         let file_path = file_path?;
36///         Ok(format!("PATH: {}", file_path))
37///     })
38///     .collect::<w::SysResult<Vec<_>>>()?;
39/// # w::SysResult::Ok(())
40/// ```
41#[must_use]
42pub fn dir_list_flat<'a>(dir_path: &'a str) -> impl Iterator<Item = SysResult<String>> + 'a {
43	DirListFlatIter::new(dir_path.to_owned())
44}
45
46/// Returns an interator over the files within a directory, and all its
47/// subdirectories, recursively.
48///
49/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
50/// iteration functions.
51///
52/// # Examples
53///
54/// ```no_run
55/// use winsafe::{self as w, prelude::*};
56///
57/// // Ordinary for loop
58/// for file_path in w::path::dir_list_recursive("C:\\Temp") {
59///     let file_path = file_path?;
60///     println!("{}", file_path);
61/// }
62///
63/// // Closure with try_for_each
64/// w::path::dir_list_recursive("C:\\Temp")
65///     .try_for_each(|file_path| {
66///         let file_path = file_path?;
67///         println!("{}", file_path);
68///         Ok(())
69///     })?;
70///
71/// // Collecting into a Vec
72/// let all = w::path::dir_list_recursive("C:\\Temp")
73///     .collect::<w::SysResult<Vec<_>>>()?;
74///
75/// // Transforming and collecting into a Vec
76/// let all = w::path::dir_list_recursive("C:\\Temp")
77///     .map(|file_path| {
78///         let file_path = file_path?;
79///         Ok(format!("PATH: {}", file_path))
80///     })
81///     .collect::<w::SysResult<Vec<_>>>()?;
82/// # w::SysResult::Ok(())
83/// ```
84#[must_use]
85pub fn dir_list_recursive<'a>(dir_path: &'a str) -> impl Iterator<Item = SysResult<String>> + 'a {
86	DirListRecursiveIter::new(dir_path.to_owned())
87}
88
89/// Returns a new string with the path of the current EXE file, without the EXE
90/// filename, and without a trailing backslash.
91///
92/// In a debug build, the `target\debug` folders will be suppressed.
93#[cfg(debug_assertions)]
94#[must_use]
95pub fn exe_path() -> SysResult<String> {
96	let dbg = HINSTANCE::NULL.GetModuleFileName()?;
97	Ok(get_path(
98		get_path(
99			get_path(&dbg).unwrap(), // exe name; go up target and debug folders
100		)
101		.unwrap(),
102	)
103	.unwrap()
104	.to_owned())
105}
106
107/// Returns a new string with the path of the current EXE file, without the EXE
108/// filename, and without a trailing backslash.
109///
110/// In a debug build, the `target\debug` folders will be suppressed.
111#[cfg(not(debug_assertions))]
112#[must_use]
113pub fn exe_path() -> SysResult<String> {
114	Ok(get_path(&HINSTANCE::NULL.GetModuleFileName()?)
115		.unwrap()
116		.to_owned())
117}
118
119/// Returns true if the path exists.
120#[must_use]
121pub fn exists(full_path: &str) -> bool {
122	GetFileAttributes(full_path).is_ok()
123}
124
125/// Extracts the file name from a full path, if any.
126///
127/// # Examples
128///
129/// ```no_run
130/// use winsafe::{self as w, prelude::*};
131///
132/// let f = w::path::get_file_name("C:\\Temp\\foo.txt"); // foo.txt
133/// ```
134#[must_use]
135pub fn get_file_name(full_path: &str) -> Option<&str> {
136	match full_path.rfind('\\') {
137		None => Some(full_path), // if no backslash, the whole string is the file name
138		Some(idx) => {
139			if idx == full_path.chars().count() - 1 {
140				None // last char is '\\', no file name
141			} else {
142				Some(&full_path[idx + 1..])
143			}
144		},
145	}
146}
147
148/// Extracts the full path, but the last part.
149///
150/// # Examples
151///
152/// ```no_run
153/// use winsafe::{self as w, prelude::*};
154///
155/// let p = w::path::get_path("C:\\Temp\\xx\\a.txt"); // C:\Temp\xx
156/// let q = w::path::get_path("C:\\Temp\\xx\\");      // C:\Temp\xx
157/// let r = w::path::get_path("C:\\Temp\\xx");        // C:\Temp"
158/// ```
159#[must_use]
160pub fn get_path(full_path: &str) -> Option<&str> {
161	full_path
162		.rfind('\\') // if no backslash, the whole string is the file name, so no path
163		.map(|idx| &full_path[0..idx])
164}
165
166/// Tells whether the full path ends in one of the given extensions,
167/// case-insensitive.
168///
169/// # Examples
170///
171/// ```no_run
172/// use winsafe::{self as w, prelude::*};
173///
174/// println!("{}",
175///     w::path::has_extension("file.txt", &["txt", "bat"]));
176/// ```
177#[must_use]
178pub fn has_extension(full_path: &str, extensions: &[impl AsRef<str>]) -> bool {
179	let full_path_u = full_path.to_uppercase();
180	extensions
181		.iter()
182		.find(|ext| {
183			let mut ext_upper = ext.as_ref().to_uppercase();
184			if !ext_upper.starts_with('.') {
185				ext_upper.insert(0, '.'); // prepend dot
186			}
187			full_path_u.ends_with(&ext_upper)
188		})
189		.is_some()
190}
191
192/// Returns true if the path is a directory. Calls
193/// [`GetFileAttributes`](crate::GetFileAttributes).
194///
195/// # Panics
196///
197/// Panics if the path does not exist.
198#[must_use]
199pub fn is_directory(full_path: &str) -> bool {
200	let flags = GetFileAttributes(full_path).unwrap();
201	flags.has(co::FILE_ATTRIBUTE::DIRECTORY)
202}
203
204/// Returns true if the path is hidden. Calls
205/// [`GetFileAttributes`](crate::GetFileAttributes).
206///
207/// # Panics
208///
209/// Panics if the path does not exist.
210#[must_use]
211pub fn is_hidden(full_path: &str) -> bool {
212	let flags = GetFileAttributes(full_path).unwrap();
213	flags.has(co::FILE_ATTRIBUTE::HIDDEN)
214}
215
216/// Replaces the file extension by the given one, returning a new string.
217///
218/// # Examples
219///
220/// ```no_run
221/// use winsafe::{self as w, prelude::*};
222///
223/// let p = w::path::replace_extension(
224///     "C:\\Temp\\something.txt", ".sh"); // C:\Temp\something.sh
225/// ```
226#[must_use]
227pub fn replace_extension(full_path: &str, new_extension: &str) -> String {
228	if let Some(last) = full_path.chars().last() {
229		if last == '\\' {
230			return rtrim_backslash(full_path).to_owned(); // full_path is a directory, do nothing
231		}
232	}
233
234	let new_has_dot = new_extension.chars().next() == Some('.');
235	match full_path.rfind('.') {
236		None => format!(
237			"{}{}{}", // file name without extension, just append it
238			full_path,
239			if new_has_dot { "" } else { "." },
240			new_extension,
241		),
242		Some(idx) => {
243			format!("{}{}{}", &full_path[0..idx], if new_has_dot { "" } else { "." }, new_extension,)
244		},
245	}
246}
247
248/// Replaces the file name by the given one, returning a new string.
249#[must_use]
250pub fn replace_file_name(full_path: &str, new_file: &str) -> String {
251	match get_path(full_path) {
252		None => new_file.to_owned(),
253		Some(path) => format!("{}\\{}", path, new_file),
254	}
255}
256
257/// Keeps the file name and replaces the path by the given one, returning a new
258/// string.
259///
260/// # Examples
261///
262/// ```no_run
263/// use winsafe::{self as w, prelude::*};
264///
265/// let p = w::path::replace_path( // C:\another\foo.txt
266///     "C:\\Temp\\foo.txt",
267///     "C:\\another",
268/// );
269/// ```
270#[must_use]
271pub fn replace_path(full_path: &str, new_path: &str) -> String {
272	let file_name = get_file_name(full_path);
273	format!(
274		"{}{}{}",
275		rtrim_backslash(new_path),
276		if file_name.is_some() { "\\" } else { "" },
277		file_name.unwrap_or("")
278	)
279}
280
281/// Removes a trailing backslash, if any.
282///
283/// # Examples
284///
285/// ```no_run
286/// use winsafe::{self as w, prelude::*};
287///
288/// let p = w::path::rtrim_backslash("C:\\Temp\\"); // C:\Temp
289/// ```
290#[must_use]
291pub fn rtrim_backslash(full_path: &str) -> &str {
292	match full_path.chars().last() {
293		None => full_path, // empty string
294		Some(last_ch) => {
295			if last_ch == '\\' {
296				let mut chars = full_path.chars();
297				chars.next_back(); // remove last char
298				chars.as_str()
299			} else {
300				full_path // no trailing backslash
301			}
302		},
303	}
304}
305
306/// Returns a `Vec` with each part of the full path.
307#[must_use]
308pub fn split_parts(full_path: &str) -> Vec<&str> {
309	let no_bs = rtrim_backslash(full_path);
310	no_bs.split('\\').collect()
311}